module dungeon.game;

import std.file;
import std.path;
import std.conv;
import std.stdio;
import std.string;

import nade.random;
import nade.sprite;
import nade.ast;
import nade.loader;

import dungeon.stats;
import dungeon.tileset;
import dungeon.mapper;
import dungeon.dice;
import dungeon.attack;
import dungeon.item;
import dungeon.item_group;
import dungeon.character;
import dungeon.monster;
import dungeon.monster_group;
import dungeon.equipment;
import dungeon.weapon;
import dungeon.armour;
import dungeon.potion;
import dungeon.race;
import dungeon.klass;
import dungeon.hud;
import dungeon.console;
import dungeon.editor;


class Game {
	protected {
		Item[string]			_items;
		ItemGroup[string]		_itemGroups;
		Weapon[]				_weapons;
		Armour[]				_armours;
		Potion[]				_potions;

		Monster[string]			_monsters;
		MonsterGroup[string]	_monsterGroups;

		Attack[string]			_attacks;

		Tileset[]				_tilesets;
		Mapper[]				_mappers;

		Race[string]			_races;
		Klass[string]			_klasses;

		Frame					_hud;
		Character				_hudCharacter = null;

		Console					_console;
	}

	this(string rootPath)
	{
		_hud = new Frame(this);
		_console = new Console(this);

		foreach(path; listdir(rootPath, "*.ast").sort)
		{
			writef("loading %s...", basename(path));
			auto ast = AST.parse(to!(string)(read(path)));
			Object[string] state = [ "game": this ];
			load(this, ast, state);
			writefln("done");
		}
	}

	Tileset getRandomTileset(int depthMin = 1, int depthMax = int.max)
	{
		Tileset[] possibles;
		int chanceTotal = 0;

		foreach(tileset; _tilesets)
		{
			if(tileset.depthMin >= depthMin  &&  tileset.depthMax <= depthMax)
			{
				possibles ~= tileset;
				chanceTotal += tileset.chance;
			}
		}

		if(chanceTotal == 0) assert(false);

		int r = random(0, chanceTotal);
		foreach(tileset; possibles)
		{
			r -= tileset.chance;
			if(r < 0) return tileset;
		}

		assert(false);
	}

	Mapper getRandomMapper(int depthMin = 1, int depthMax = int.max)
	{
		Mapper[] possibles;
		int chanceTotal = 0;

		foreach(mapper; _mappers)
		{
			if(mapper.depthMin >= depthMin  &&  mapper.depthMax <= depthMax)
			{
				possibles ~= mapper;
				chanceTotal += mapper.chance;
			}
		}

		if(chanceTotal == 0) assert(false);

		int r = random(0, chanceTotal);
		foreach(mapper; possibles)
		{
			r -= mapper.chance;
			if(r < 0) return mapper;
		}

		assert(false);
	}

	void addItem(Item item)
	{
		_items[item.id] = item;
	}
	Item getItem(string id)
	{
		auto item = id in _items;
		if(item is null) assert(false, id);
		return *item;
	}

	void addItemGroup(ItemGroup group)
	{
		_itemGroups[group.id] = group;
	}
	ItemGroup getItemGroup(string id)
	{
		auto group = id in _itemGroups;
		if(group is null) assert(false, id);
		return *group;
	}

	void addAttack(Attack attack)
	{
		_attacks[attack.id] = attack;
	}
	Attack getAttack(string id)
	{
		auto attack = id in _attacks;
		if(attack is null) assert(false, id);
		return *attack;
	}
	Attack getAttack(AST ast, Object[string] state)
	{
		if(ast.length >= 2  &&  ast[0].length == 0  &&  ast[1].length == 0)
		{
			return getAttack(ast[0].as!string).getChild(ast[1..ast.length], state);
		}
		else if(ast.length >= 1  &&  ast[0].length == 0)
		{
			auto child = getAttack(ast[0].as!string);
			load(child, ast[1..ast.length], state);
			return child;
		}

		auto child = new Attack(this);
		load(child, ast, state);
		return child;
	}

	void addMonster(Monster monster)
	{
		_monsters[monster.id] = monster;
	}
	Monster getMonster(string id)
	{
		auto monster = id in _monsters;
		if(monster is null) assert(false, id);
		return *monster;
	}

	void addMonsterGroup(MonsterGroup group)
	{
		_monsterGroups[group.id] = group;
	}
	MonsterGroup getMonsterGroup(string id)
	{
		auto group = id in _monsterGroups;
		if(group is null) assert(false, id);
		return *group;
	}

	void addRace(Race race)
	{
		_races[race.id] = race;
	}
	Race getRace(string id)
	{
		auto race = id in _races;
		if(race is null) assert(false, id);
		return *race;
	}

	void addKlass(Klass klass)
	{
		_klasses[klass.id] = klass;
	}
	Klass getKlass(string id)
	{
		auto klass = id in _klasses;
		if(klass is null) assert(false, id);
		return *klass;
	}

	Frame hud() { return _hud; }

	Console console() { return _console; }

	Character hudCharacter() { return _hudCharacter; }
	Character hudCharacter(Character rhs) { return _hudCharacter = rhs; }

	static void install()
	{
		Loader!(Game).add("tileset",
			(ref Game game, AST ast, Object[string] state)
			{
				auto tileset = new Tileset;
				game._tilesets ~= tileset;
				load(tileset, ast, state);
			}
		);

		Loader!(Game).add("attack",
			(ref Game game, AST ast, Object[string] state)
			{
				game.getAttack(ast, state);
			}
		);

		Loader!(Game).add("weapon",
			(ref Game game, AST ast, Object[string] state)
			{
				auto weapon = new Weapon(game);
				game._weapons ~= weapon;
				load(weapon, ast, state);
			}
		);
		Loader!(Game).add("armour",
			(ref Game game, AST ast, Object[string] state)
			{
				auto armour = new Armour(game);
				game._armours ~= armour;
				load(armour, ast, state);
			}
		);
		Loader!(Game).add("potion",
			(ref Game game, AST ast, Object[string] state)
			{
				auto potion = new Potion(game);
				game._potions ~= potion;
				load(potion, ast, state);
			}
		);

		Loader!(Game).add("item_group",
			(ref Game game, AST ast, Object[string] state)
			{
				auto group = new ItemGroup(game);
				load(group, ast, state);
			}
		);

		Loader!(Game).add("monster",
			(ref Game game, AST ast, Object[string] state)
			{
				auto monster = new Monster(game);
				load(monster, ast, state);
			}
		);

		Loader!(Game).add("monster_group",
			(ref Game game, AST ast, Object[string] state)
			{
				auto group = new MonsterGroup(game);
				load(group, ast, state);
			}
		);

		Loader!(Game).add("stat",
			(ref Game game, AST ast, Object[string] state)
			{
				auto stat = new StatInfo;
				load(stat, ast, state);
			}
		);

		Loader!(Game).add("mapper",
			(ref Game game, AST ast, Object[string] state)
			{
				auto mapper = new Mapper;
				game._mappers ~= mapper;
				load(mapper, ast, state);
			}
		);

		Loader!(Game).add("race",
			(ref Game game, AST ast, Object[string] state)
			{
				auto race = new Race(game);
				load(race, ast, state);
			}
		);

		Loader!(Game).add("class",
			(ref Game game, AST ast, Object[string] state)
			{
				auto klass = new Klass(game);
				load(klass, ast, state);
			}
		);

		Loader!(Game).add("window",
			(ref Game game, AST ast, Object[string] state)
			{
				auto window = new Window(game, game.hud);
				game.hud.addChild(window);
				load(window, ast, state);
			}
		);

		/********* console commands *********/
		Loader!(CommandResult).add("tilesets",
			(ref CommandResult cmd, AST ast, Object[string] state)
			{
				cmd.console.write("");
				cmd.console.write("  Global Tileset List");
				cmd.console.write("-------------------------------------------------------");
				cmd.console.write("| ID               | NAME                             |");
				cmd.console.write("-------------------------------------------------------");
				foreach(tileset; cmd.game._tilesets)
				{
					cmd.console.writef("| %-16s | %-32s |", tileset.id, tileset.name);
				}
				cmd.console.write("-------------------------------------------------------");
				cmd.console.write("");
			}
		);

		Loader!(CommandResult).add("tileset",
			(ref CommandResult cmd, AST ast, Object[string] state)
			{
				void usage()
				{
					cmd.console.write("Usage: tileset <id> [(<property> <value>) ...]");
					cmd.console.write("");
				}

				if(ast.length < 1)
				{
					usage();
					return;
				}
				auto id = ast[0].as!string;

				foreach(tileset; cmd.game._tilesets)
				{
					if(tileset.id == id)
					{
						foreach(node; ast[1 .. ast.length])
						{
							if(node.length < 1)
							{
								usage();
								return;
							}

							switch(node.label)
							{
								case "id":				tileset.id = node[0].as!string; break;
								case "name":			tileset.name = node[0].as!string;
								case "depthMin":	tileset.depthMin = node[0].as!int; break;
								case "depthMax":	tileset.depthMax = node[0].as!int; break;
								case "chance":		tileset.chance = node[0].as!int; break;
								default:					cmd.console.writef("Invalid tileset mutator: %s", node);
							}
						}

						cmd.console.write("");
						cmd.console.write("  Tileset Properties");
						cmd.console.write("-------------------------------------------------------");
						cmd.console.write("| NAME             | VALUE                            |");
						cmd.console.write("-------------------------------------------------------");
						cmd.console.writef("| %-16s | %-32s |", "id", tileset.id);
						cmd.console.writef("| %-16s | %-32s |", "name", tileset.name);
						cmd.console.writef("| %-16s | %-32s |", "depthMin", tileset.depthMin);
						cmd.console.writef("| %-16s | %-32s |", "depthMax", tileset.depthMax);
						cmd.console.writef("| %-16s | %-32s |", "chance", tileset.chance);
						cmd.console.write("-------------------------------------------------------");
					}
				}
				cmd.console.write("");
			}
		);
	}
}
